import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { showFullScreenLoading, tryHideFullScreenLoading } from "@/config/serviceLoading";
import { LOGIN_URL } from "@/config";
import { ElMessage } from "element-plus";
// import { ResultData } from "@/api/interface";
import { ResultEnum } from "@/enums/httpEnum";
import { checkStatus } from "./helper/checkStatus";
import { useUserStore } from "@/stores/modules/user";
import router from "@/routers";

export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
  showLoading?: boolean;
}
// 开发中应逐步补充
type HttpResCode =
  | 1000 // 权限校验不通过
  | 1001 // 登录失效
  | 2000 // success
  | 3000 // 业务异常
  | 4000 // 客户端请求错误
  | 5000 // 服务端异常
  | 200 // success
  | 201 // success
  | 304 // success
  | 301 // success
  | 404 //
  | 403 //
  | 402 //
  | 401 //
  | 500 //
  | 502 //
  | 503 //
  | 504; //

export type HttpResRaw<D = unknown> = {
  msg: string;
  data: D;
  code: HttpResCode;
};

export type HttpRes<R, C = HttpResCode> = [err: null, res: R, code: C] | [err: string, res: R | null, code: C]; // 兼容 code 非 200 时 data 可用，实属接口不规范

export interface HttpInstance extends AxiosInstance {
  request<T, R = HttpRes<T>, D = unknown>(config: AxiosRequestConfig<D> & { showLoading?: boolean }): Promise<R>;
  get<T, R = HttpRes<T>, D = unknown>(url: string, config?: AxiosRequestConfig<D> & { showLoading?: boolean }): Promise<R>;
  delete<T, R = HttpRes<T>, D = unknown>(url: string, config?: AxiosRequestConfig<D> & { showLoading?: boolean }): Promise<R>;
  head<T, R = HttpRes<T>, D = unknown>(url: string, config?: AxiosRequestConfig<D> & { showLoading?: boolean }): Promise<R>;
  options<T, R = HttpRes<T>, D = unknown>(url: string, config?: AxiosRequestConfig<D> & { showLoading?: boolean }): Promise<R>;
  post<T, R = HttpRes<T>, D = unknown>(
    url: string,
    data?: D,
    config?: AxiosRequestConfig<D> & { showLoading?: boolean }
  ): Promise<R>;
  put<T, R = HttpRes<T>, D = unknown>(
    url: string,
    data?: D,
    config?: AxiosRequestConfig<D> & { showLoading?: boolean }
  ): Promise<R>;
  patch<T, R = HttpRes<T>, D = unknown>(
    url: string,
    data?: D,
    config?: AxiosRequestConfig<D> & { showLoading?: boolean }
  ): Promise<R>;
}

const config = {
  // 默认地址请求地址，可在 .env.** 文件中修改
  baseURL: import.meta.env.VITE_API_URL as string,
  // 设置超时时间
  timeout: ResultEnum.TIMEOUT as number,
  // 跨域时候允许携带凭证
  withCredentials: true
};

/** TODO: 待接口替换移除code200； */
const successCodes = [200, 2000]; // 请求成功的code

function createHttp(): HttpInstance {
  const http = axios.create(config);

  http.interceptors.request.use(setRequestCredentials, (error: AxiosError) => {
    return Promise.reject(error);
  });

  http.interceptors.response.use(printResponseWarning);

  // 此拦截器会改变响应体，拦截后仅返回接口数据
  // 因此要在最后注册！
  http.interceptors.response.use(transformResponse as any, HandleError);

  return http as HttpInstance;
}

/**
 * 请求拦截器。
 * 在请求发出时，设置凭证信息，如 token
 */
function setRequestCredentials(config: CustomAxiosRequestConfig) {
  const userStore = useUserStore();
  // 当前请求不需要显示 loading，在 api 服务中通过指定的第三个参数: { showLoading: true } 来控制
  config.showLoading && showFullScreenLoading();
  if (config.headers && typeof config.headers.set === "function") {
    config.headers.set("x-access-token", userStore.token);
  }
  return config;
}

/**
 * 响应拦截器。
 * 当请求的响应值存在风险时，在控制台打印出数据明细，
 * 以方便开发者调试，同时弹窗显示返回的错误消息。
 */
function printResponseWarning(response: AxiosResponse): AxiosResponse {
  const { method, url, headers } = response.config;
  const { code, msg, data } = resolveData(response);
  const SUCCESS = successCodes.includes(Number(code));
  const request = response.config;

  const userStore = useUserStore();
  tryHideFullScreenLoading();
  // 登陆失效
  if (code == ResultEnum.OVERDUE) {
    userStore.setToken("");
    router.replace(LOGIN_URL);
    ElMessage.error(msg);
  }

  // 错误警告 或者 data 为 null 警告
  if (!SUCCESS || (SUCCESS && data == null)) {
    warn(code, msg || "No Data!", method, url, request, data);
  }

  // 错误弹窗，可通过特定的 header 配置禁用
  if (!SUCCESS && !headers?.["x-suppress-warning"]) {
    ElMessage.warning(msg);
  }
  return response;
}

/**
 * 控制台输出警告
 */
function warn(code: number, msg: string, method = "", url = "", req: unknown, res: unknown) {
  console.group(`%c ${code} %c ${msg} `, "color:yellow; background: black", "background: yellow; color: red");
  console.log(`%c ${method?.toUpperCase()} %c ${url} `, "background: green; color: white", "background: #eeecda");
  console.log("%c req ", "background: grey; color: white", req);
  console.log("%c res ", "background: black; color: white", res);
  console.groupEnd();
}

/**
 * 解析 axios response.
 * 返回 `msg` `code` `data` 字段
 */
function resolveData(response: AxiosResponse<HttpResRaw>) {
  if (response.data instanceof Blob || typeof response.data === "string") {
    return {
      code: response.status,
      msg: response.statusText,
      data: response.data
    };
  }

  // hack: 兼容不规范的状态码
  if (typeof response.data.code === "string") {
    response.data.code = Number(response.data.code) as HttpResCode;
    console.error("后端接口返回状态码不规范！code 是字符串" + `'${response.data.code}'`);
  }

  return response.data;
}

/**
 * 转换响应值为 `error first` 的形式
 */
function transformResponse(response: AxiosResponse<HttpResRaw>): HttpRes<unknown> {
  const { code, msg, data } = resolveData(response);
  const error = successCodes.includes(code) ? null : msg;
  return [error, data, code as HttpResCode];
}

/**
 *
 * 接口异常http status非200错误处理
 * @returns
 */
function HandleError(err: any) {
  console.log("err=>>>>>>", err);
  const { status, data } = err.response;
  const { url, method } = err.config;
  const message = err.message;
  ElMessage.error(message);
  warn(status, message, method, url, err.config, data);

  const { response } = err;
  tryHideFullScreenLoading();
  // 请求超时 && 网络错误单独判断，没有 response
  if (message.indexOf("timeout") !== -1) ElMessage.error("请求超时！请您稍后重试");
  if (message.indexOf("Network Error") !== -1) ElMessage.error("网络错误！请您稍后重试");
  // 根据服务器响应的错误状态码，做不同的处理
  if (response) checkStatus(status);
  // 服务器结果都没有返回(可能服务器错误可能客户端断网)，断网处理:可以跳转到断网页面
  if (!window.navigator.onLine) router.replace("/500");

  return transformResponse(err.response);
}

export const http = createHttp();
